/*
* Nettle Source File.
* Copyright (C) 2013, Nettle (http://nttl.ru/).
*
* This file is part of NettleMath.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this diLibution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is diLibuted in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Description:
*
* History:
* Created by Sergey Serb, l-proger@yandex.ru
*
* Code initially inspired by ftp://www.fox-toolkit.org/pub/fasthalffloatconversion.pdf document + wikipedia
* http://en.wikipedia.org/wiki/Half_precision_floating-point_format
*/

#ifndef NHalf_h__
#define NHalf_h__

#include "NMathTypes.h"
#include <string>
#include <sstream>

namespace NettleMath{

   /* class NHalf;
    const NHalf toHalf(float src);
    const float toFloat(const NHalf& src);
    */
    class NHalf{
    private:
        struct FI32{
            union{
                float f;
                uint32 i;
            };
        };
        uint16 data;
    public:
        std::string toLing(){
            std::stringstream ss;
            ss << this->toFloat();
            return ss.str();
        }
        bool isNan(){
            return ((data & 0x7c00) == 0x7c00) && ((data & 0x3ff) == 0);
        }
        bool isInf(){
            return ((data & 0x7c00) == 0x7c00) && ((data & 0x3ff) != 0);
        }
        bool isSubnormal(){
            return ((data & 0x7c00) == 0) && ((data & 0x3ff) != 0);
        }
        bool isPositive(){
            return (data & 0x8000) == 0;
        }
        bool isZero(){
            return (data & 0x7fff) == 0;
        }
        const float toFloat() const{
            return toFloat(*this);
        }
        static const NHalf toHalf(float src){
            NHalf dst;
            FI32 fi;
            fi.f = src;
            int32 s,m,e;

            //fp32 exponent zero offset = 127
            //fp16 exponent zero offset = 15

            s = (fi.i >> 16) & 0x8000; //-V519
            m = fi.i & 0x7fffff; //-V519
            //decode exponent from fp32 and encode to fp16
            e = ((fi.i >> 23) & 0xff) - 127 + 15; //-V519

            if ((e > 0) && (e < 30)){ 
                //simple case, no exp overflow, just loose precision
                dst.data = static_cast<uint16>((s) | (e << 10) | ((m + 0x1000) >> 13));
            }else if(src == 0.0f){
                //handle zero
                dst.data = 0;
            }else{
                //subnormal values
                if(e <= 0){
                    if(e < -10){ //-10-15 = -25(exp) - too small value to represent in half float
                        dst.data = 0;
                    }else{//just subnormal value, representable in half
                        //add explicitly 1 leading bit
                        m = (m | 0x800000) >> (-e + 1);

                        //round to bigger value
                        if(m & 0x1000)
                            m |= 0x2000;
                        dst.data = static_cast<uint16>((s) | (m >> 13));
                    }
                }else if(e == 0xff - 127 + 15){//exp exactly on NAN or INF
                    //mantissa == 0 and exp == 11111 then inf
                    //else, if mantissa  != 0 -> NAN
                    if(m == 0){//INF
                        dst.data = static_cast<uint16>(s | 0x7c00);
                    }else{//NAN
                        dst.data = static_cast<uint16>(s | 0x7c00 | (m >> 13));
                    }
                }else{//Exp should be > 0  (> 30 or 0 + 1 (rounded))
                    //round
                    if(m & 0x1000){
                        m += 0x2000;
                        if(m & 0x800000){
                            m = 0;
                            e++;
                        }
                    }
                    if(e > 30){//exp overflow
                        dst.data = static_cast<uint16>(s | 0x7c00);//too big val, inf
                    }else{//
                        dst.data = static_cast<uint16>((s) | (e << 10) | (m >> 13));
                    }
                }
            }
            return dst;
        }
        static const float toFloat(const NHalf& src){
            int s, e, m;
            FI32 fi;
            s = src.data >> 15;
            e = (src.data & 0x7c00) >> 10;
            m = (src.data & 0x3ff);

            if(e > 0 && e < 31){//normalized val
                fi.i = (s << 31) | ((e - 15 + 127) << 23) | (m << 13);
            }else if((e == 0) && (m == 0)){//signed zero
                fi.i = s << 31;
            }else if((e == 0) && (m != 0)){//denormalized value
                //normalize value
                while(!(m & 0x400)){
                    m <<= 1;
                    e--;
                }
                e++;
                m = m & 0x3ff;
                fi.i = (s << 31) | ((e - 15 + 127) << 23) | (m << 13);
            }else if((e == 31) && (m == 0)){//INF
                fi.i = (s << 31) | 0x7f800000;
            }else{
                fi.i = (s << 31) | 0x7f800000 | (m << 13);
            }
            return fi.f;
        }

        NHalf(){

        }
        NHalf(const NHalf& val){
            data = val.data;
        }
        NHalf(const float& val){
            *this = toHalf(val);
        }
        NHalf& operator =(const float& other){
            *this = toHalf(other);
            return *this;
        }
       /* operator float(){
            return toFloat(*this);
        }*/
       /* operator float() const{
            return toFloat(*this);
        }*/

        //operators
        NHalf operator +(){
            return *this;
        }
        NHalf operator -(){
            return toHalf(-toFloat());
        }
        NHalf operator +(const NHalf& rval){
            return toHalf(this->toFloat() + rval.toFloat());
        }
        NHalf operator -(const NHalf& rval){
            return toHalf(this->toFloat() - rval.toFloat());
        }
        NHalf operator *(const NHalf& rval){
            return toHalf(this->toFloat() * rval.toFloat());
        }
        NHalf operator /(const NHalf& rval){
            return toHalf(this->toFloat() / rval.toFloat());
        }

        bool operator ==(const NHalf& rval){
            return data == rval.data;
        }
        bool operator !=(const NHalf& rval){
            return data != rval.data;
        }
        bool operator <(const NHalf& rval){
            return this->toFloat() < rval.toFloat();
        }
        bool operator >(const NHalf& rval){
            return this->toFloat() > rval.toFloat();
        }
        bool operator <=(const NHalf& rval){
            return this->toFloat() <= rval.toFloat();
        }
        bool operator >=(const NHalf& rval){
            return this->toFloat() >= rval.toFloat();
        }
        NHalf& operator +=(const NHalf& rval){
            *this = toHalf(this->toFloat() + rval.toFloat());
            return *this;
        }
        NHalf& operator -=(const NHalf& rval){
            *this = toHalf(this->toFloat() - rval.toFloat());
            return *this;
        }
        NHalf& operator *=(const NHalf& rval){
            *this = toHalf(this->toFloat() * rval.toFloat());
            return *this;
        }
        NHalf& operator /=(const NHalf& rval){
            *this = toHalf(this->toFloat() / rval.toFloat());
            return *this;
        }

        const NHalf& operator++(){
            *this = toHalf(this->toFloat() + 1.0f);
            return *this;
        }
        const NHalf operator++(int){
            NHalf old(*this);
            *this = toHalf(this->toFloat() + 1.0f);
            return old;
        }
        const NHalf& operator--(){
            *this = toHalf(this->toFloat() - 1.0f);
            return *this;
        }
        const NHalf operator--(int){
            NHalf old(*this);
            *this = toHalf(this->toFloat() - 1.0f);
            return old;
        }
    };

    typedef NHalf half;
}
#endif // NHalf_h__